<?php
/**
 * 安全性非常高的可逆加解密类
 * 支持自定义加解密公钥和私钥
 * 支持生成动态密文（即同一字符串每次生成的密文不同）
 */
namespace jansen\utils\crypt;
class Crypt{
    /**
     * @var string $chars 密文所用字符
     */
    private $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
    /**
     * @var string $publicKey 加密公钥
     */
    private $publicKey = '-x6g6ZWm2G9g_vr0Bo.pOq3kRIxsZ6rm';
    /**
     * @var string $privateKey 加密私钥
     */
    private $privateKey = '@YUDMVkwi3#tyqH4';
    /**
     * @var bool $dynamic 是否生成动态密文
     */
    private $dynamic = true;
    /**
     * 构造器
     *
     * @param string $privateKey 私钥字符串
     * @param string $publicKey 公钥字符串
     */
    public function __construct(string $privateKey = '', string $publicKey = ''){
        $this->setPrivateKey($privateKey);
        $this->setPublicKey($publicKey);
    }
    /**
     * 设置公钥
     * @param string $key 公钥字符串
     * @author:Jansen <6206574@qq.com>
     */
    public function setPublicKey(string $key=''){
        if (!empty($key)){
            $this->publicKey = $key;
        }
    }
    /**
     * 设置私钥
     * @param string $key 私钥字符串
     * @author:Jansen <6206574@qq.com>
     */
    public function setPrivateKey(string $key=''){
        if (!empty($key)){
            $this->privateKey = $key;
        }
    }
    /**
     * 设置是否生成动态密文
     * @param bool $dynamic
     * @author:Jansen <6206574@qq.com>
     */
    public function setDynamic(bool $dynamic = true){
        $this->dynamic = $dynamic;
    }
    /**
     * 加密
     * @param string $text 待加密字符串
     * @return string
     * @author:Jansen <6206574@qq.com>
     */
    public function encrypt(string $text){
        if ($this->dynamic){
            [$nh1, $nh2, $nh3] = [rand(0, 64), rand(0, 64), rand(0, 64)];
        }else{
            [$nh1, $nh2, $nh3] = [20, 40, 60];
        }
        $ch1 = $this->chars[$nh1];
        $ch2 = $this->chars[$nh2];
        $ch3 = $this->chars[$nh3];
        $nhnum = $nh1 + $nh2 + $nh3;
        $knum = 0;
        $i = 0;
        while(isset($this->privateKey[$i])){
            $knum += ord($this->privateKey[$i++]);
        }
        $mdKey = substr(md5(md5(md5($this->privateKey.$ch1).$ch2.$this->publicKey).$ch3), $nhnum%8, $knum%8 + 16);
        $text = base64_encode($text);
        $text = str_replace(array('+', '/', '='), array('-', '_', '.'), $text);
        $result = '';
        $k = 0;
        $tlen = strlen($text);
        $klen = strlen($mdKey);
        for ($i=0; $i<$tlen; $i++) {
            $k = $k == $klen ? 0 : $k;
            $j = ($nhnum + strpos($this->chars, $text[$i]) + ord($mdKey[$k++])) % 64;
            $result .= $this->chars[$j];
        }
        $resultLength = strlen($result);
        $result = substr_replace($result, $ch3, $nh2 % ++$resultLength, 0);
        $result = substr_replace($result, $ch2, $nh1 % ++$resultLength, 0);
        return substr_replace($result, $ch1, $knum % ++$resultLength, 0);
    }

    /**
     * 解密
     * @param string $text 待解密字符串
     * @return string
     * @author:Jansen <6206574@qq.com>
     */
    public function decrypt(string $text){
        $knum = 0;
        $i = 0;
        $tlen = strlen($text);
        while(isset($this->privateKey[$i])){
            $knum += ord($this->privateKey[$i++]);
        }
        $ch1 = $text[$knum % $tlen];
        $nh1 = strpos($this->chars, $ch1);
        $text = substr_replace($text, '', $knum % $tlen--, 1);
        $ch2 = $text[$nh1 % $tlen];
        $nh2 = strpos($this->chars, $ch2);
        $text = substr_replace($text, '', $nh1 % $tlen--, 1);
        $ch3 = $text[$nh2 % $tlen];
        $nh3 = strpos($this->chars, $ch3);
        $text = substr_replace($text, '', $nh2 % $tlen--, 1);
        $nhnum = $nh1 + $nh2 + $nh3;
        $mdKey = substr(md5(md5(md5($this->privateKey.$ch1).$ch2.$this->publicKey).$ch3), $nhnum % 8, $knum % 8 + 16);
        $result = '';
        $k = 0;
        $tlen = strlen($text);
        $klen = strlen($mdKey);
        for ($i=0; $i<$tlen; $i++) {
            $k = $k == $klen ? 0 : $k;
            $j = strpos($this->chars, $text[$i]) - $nhnum - ord($mdKey[$k++]);
            while ($j<0) $j+=64;
            $result .= $this->chars[$j];
        }
        $result = str_replace(array('-', '_', '.'), array('+', '/', '='), $result);
        return trim(base64_decode($result));
    }

}